package top.wilsonlv.jaguar.cloud.auth.config;

import com.anji.captcha.service.CaptchaService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import top.wilsonlv.jaguar.cloud.auth.extension.device.DeviceAuthenticationProvider;
import top.wilsonlv.jaguar.cloud.auth.extension.device.DeviceTokenGranter;
import top.wilsonlv.jaguar.cloud.auth.extension.face.FaceAuthenticationProvider;
import top.wilsonlv.jaguar.cloud.auth.extension.face.FaceTokenGranter;
import top.wilsonlv.jaguar.cloud.auth.extension.sms.SmsAuthenticationProvider;
import top.wilsonlv.jaguar.cloud.auth.extension.sms.SmsCodeTokenGranter;
import top.wilsonlv.jaguar.cloud.auth.extension.sms.SmsService;
import top.wilsonlv.jaguar.cloud.auth.oauth2.AuthServerTokenServices;
import top.wilsonlv.jaguar.cloud.auth.oauth2.RedisAuthorizationCodeServices;
import top.wilsonlv.jaguar.cloud.auth.oauth2.RedisClientDetailsServiceImpl;
import top.wilsonlv.jaguar.cloud.auth.oauth2.ResourceOwnerPasswordCaptchaTokenGranter;
import top.wilsonlv.jaguar.cloud.auth.security.UserDetailsServiceImpl;
import top.wilsonlv.jaguar.cloud.handlerlog.client.component.HandlerLogInterceptor;
import top.wilsonlv.jaguar.security.component.AuthenticationExceptionHandler;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lvws
 * @since 2021/4/8
 */
@Configuration
@RequiredArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

    private final AuthServerTokenServices authServerTokenServices;

    private final RedisAuthorizationCodeServices authorizationCodeServices;

    private final AuthenticationManager authenticationManager;

    private final UserDetailsServiceImpl userDetailsService;

    private final RedisClientDetailsServiceImpl clientDetailsService;

    private final AuthenticationExceptionHandler authenticationExceptionHandler;

    private final HandlerLogInterceptor handlerLogInterceptor;

    private final CaptchaService captchaService;

    @Autowired(required = false)
    private SmsService smsService;

    @Autowired(required = false)
    private SmsAuthenticationProvider smsAuthenticationProvider;

    @Autowired(required = false)
    private FaceAuthenticationProvider faceAuthenticationProvider;

    @Autowired(required = false)
    private DeviceAuthenticationProvider deviceAuthenticationProvider;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenServices(authServerTokenServices)
                .tokenGranter(compositeTokenGranter(endpoints))
                .authorizationCodeServices(authorizationCodeServices)
                .pathMapping("/oauth/error", "/oauthRedirectPage/error")
                .pathMapping("/oauth/confirm_access", "/oauthRedirectPage/confirm_access")
                .addInterceptor(handlerLogInterceptor);
    }

    private CompositeTokenGranter compositeTokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> tokenGranters = new ArrayList<>();
        tokenGranters.add(endpoints.getTokenGranter());

        ResourceOwnerPasswordCaptchaTokenGranter captchaTokenGranter = new ResourceOwnerPasswordCaptchaTokenGranter(authenticationManager,
                authServerTokenServices, clientDetailsService, endpoints.getOAuth2RequestFactory(), captchaService);
        tokenGranters.add(captchaTokenGranter);

        if (smsAuthenticationProvider != null) {
            SmsCodeTokenGranter smsCodeTokenGranter = new SmsCodeTokenGranter(new ProviderManager(smsAuthenticationProvider),
                    authServerTokenServices, clientDetailsService, endpoints.getOAuth2RequestFactory(), smsService);
            tokenGranters.add(smsCodeTokenGranter);
        }

        if (faceAuthenticationProvider != null) {
            FaceTokenGranter faceTokenGranter = new FaceTokenGranter(new ProviderManager(faceAuthenticationProvider),
                    authServerTokenServices, clientDetailsService, endpoints.getOAuth2RequestFactory());
            tokenGranters.add(faceTokenGranter);
        }

        if (deviceAuthenticationProvider != null) {
            DeviceTokenGranter deviceTokenGranter = new DeviceTokenGranter(new ProviderManager(deviceAuthenticationProvider),
                    authServerTokenServices, clientDetailsService, endpoints.getOAuth2RequestFactory());
            tokenGranters.add(deviceTokenGranter);
        }
        return new CompositeTokenGranter(tokenGranters);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients()
                .authenticationEntryPoint(authenticationExceptionHandler)
                .checkTokenAccess("isAuthenticated()");
    }

}
